package vfs.obj.entity.impl;

import java.sql.Connection;
import vfs.except.VFSPersistenceException;
import vfs.filter.VFSFilter;
import vfs.obj.entity.VFSEntityFactory;
import vfs.obj.entity.VFSNodeType;
import vfs.obj.entity.VFSGroup;
import vfs.obj.entity.VFSNode;
import vfs.obj.entity.VFSUser;
import vfs.persist.entity.manage.VFSEntityManager;
import vfs.persist.entity.build.impl.VFSNodeTypeBuilder;
import vfs.persist.entity.build.impl.VFSGroupBuilder;
import vfs.persist.entity.build.impl.VFSNodeBuilder;
import vfs.persist.entity.build.impl.VFSUserBuilder;
import vfs.persist.entity.manage.impl.VFSEntityManagerImpl;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import vfs.except.VFSCreationException;
import vfs.except.VFSEntityException;
import vfs.except.VFSModificationException;
import vfs.except.VFSUncheckedException;
import vfs.filter.VFSFilter.VFSFilterOperator;
import vfs.filter.impl.VFSFilterImpl;
import vfs.obj.association.VFSAssociationFactory;
import vfs.obj.association.VFSNodeToNodeType;
import vfs.obj.association.VFSPath;
import vfs.obj.association.impl.VFSAssociationFactoryImpl;
import vfs.obj.entity.VFSPathAncestor;
import vfs.obj.entity.VFSPathDescendant;
import vfs.persist.util.VFSPersistUtil;

/**
 *
 * @author Matthew Eavenson <durandal@uga.edu>
 */
public class VFSEntityFactoryImpl implements VFSEntityFactory{
    
    private Connection conn;
    private VFSAssociationFactory aFactory;
    private VFSEntityManager<VFSUser> userManager;
    private VFSEntityManager<VFSGroup> groupManager;
    private VFSEntityManager<VFSNodeType> nodeTypeManager;
    private VFSEntityManager<VFSNode> nodeManager;
    private VFSNodeToNodeType nft;
    private VFSPath path;
    
    public VFSEntityFactoryImpl(Connection conn){
        this.conn = conn;
        aFactory = new VFSAssociationFactoryImpl(conn);
        nft = aFactory.getNodeToNodeType();
        path = aFactory.getPath();
        userManager = new VFSEntityManagerImpl<VFSUser>(conn, new VFSUserBuilder());
        groupManager = new VFSEntityManagerImpl<VFSGroup>(conn, new VFSGroupBuilder());
        nodeTypeManager = new VFSEntityManagerImpl<VFSNodeType>(conn, new VFSNodeTypeBuilder());
        nodeManager = new VFSEntityManagerImpl<VFSNode>(conn, new VFSNodeBuilder());
    }
    
    // add a new user
    @Override
    public VFSUser createUser(String name, VFSNode home) {
        VFSUser user = new VFSUserImpl(name);
        Map<String,String> foreignKeys = new HashMap<String,String>();
        foreignKeys.put("home", home.getId()+"");
        userManager.add(user, foreignKeys);
        return user;
    }

    // add a new group
    @Override
    public VFSGroup createGroup(String name) {
        VFSGroup group = new VFSGroupImpl(name);
        groupManager.add(group);
        return group;
    }

    // add a new file type
    @Override
    public VFSNodeType createNodeType(String name, String description) {
        VFSNodeType nodeType = new VFSNodeTypeImpl(name, description);
        nodeTypeManager.add(nodeType);
        return nodeType;
    }

    // add a new node
    @Override
    public VFSNode createNode(String name, String location, VFSNode.PublicAccess access, VFSUser owner, VFSNode parent, boolean transactional) {
        
        // parent node must be a folder
        if(nft.getNodeType(parent) != null)
            throw new VFSEntityException("Destination is not a folder");
        
        Date now = new Date();
        VFSNode node = new VFSNodeImpl(name, location, now, now, access);
        
        Map<String, String> foreignKeys = new HashMap<String, String>();
        foreignKeys.put("ownedBy", owner.getId()+"");
        foreignKeys.put("parent", parent.getId()+"");
        
        try{
            if(transactional){
                // begin transaction
                VFSPersistUtil.disableAutoCommit(conn);
            }
            nodeManager.add(node, foreignKeys);
            // add path information
            Iterator<VFSPathAncestor> ancestors = path.getAncestors(parent, null);

            while( ancestors.hasNext() ){
                VFSPathAncestor ancestor = ancestors.next();
                path.addPath(ancestor.getAncestor(), node, ancestor.length()+1);
            }
            path.addPath(parent, node, 1);
            
            if(transactional){
                // end transaction
                VFSPersistUtil.commit(conn);
                // reenable autocommit
                VFSPersistUtil.enableAutoCommit(conn);
            }
        }catch(VFSPersistenceException ex){
            if(transactional){
                // end transaction
                VFSPersistUtil.rollback(conn);
                // reenable autocommit
                VFSPersistUtil.enableAutoCommit(conn);
            }
            throw new VFSCreationException(ex);
        }
        return node;
    }
    
    // add a new node
    @Override
    public VFSNode createNode(String name, String location, VFSNode.PublicAccess access, VFSUser owner, VFSNode parent) {
        return createNode(name, location, access, owner, parent, true);
    }
    
    @Override
    public void storeUser(VFSUser user) {
        userManager.update(user);
    }

    @Override
    public void storeGroup(VFSGroup group) {
        groupManager.update(group);
    }

    @Override
    public void storeNodeType(VFSNodeType nodeType) {
        nodeTypeManager.update(nodeType);
    }

    @Override
    public void storeNode(VFSNode node) {        
        nodeManager.update(node);
    }
    
    @Override
    public Iterator<VFSUser> findUsers(VFSFilter filter) {
        return userManager.get(filter);
    }

    @Override
    public Iterator<VFSGroup> findGroups(VFSFilter filter) {
        return groupManager.get(filter);
    }

    @Override
    public Iterator<VFSNodeType> findNodeTypes(VFSFilter filter) {
        return nodeTypeManager.get(filter);
    }

    @Override
    public Iterator<VFSNode> findNodes(VFSFilter filter) {        
        return nodeManager.get(filter);
    }

    @Override
    public void removeUser(VFSFilter filter) {
        userManager.remove(filter);
    }

    @Override
    public void removeGroup(VFSFilter filter) {
        groupManager.remove(filter);
    }

    @Override
    public void removeNodeType(VFSFilter filter) {
        nodeTypeManager.remove(filter);
    }

    @Override
    public void removeNode(VFSFilter filter) {        
        for(Iterator<VFSNode> nodes = nodeManager.get(filter); nodes.hasNext();){
            VFSNode node = nodes.next();
            try{
                removePath(node);
            }catch(VFSPersistenceException ex){
                throw new VFSModificationException(ex);
            }
        }
    }
    
    @Override
    public void removeUser(VFSUser user) {
        userManager.remove(user);
    }

    @Override
    public void removeGroup(VFSGroup group) {
        groupManager.remove(group);
    }

    @Override
    public void removeNodeType(VFSNodeType nodeType) {
        nodeTypeManager.remove(nodeType);
    }

    @Override
    public void removeNode(VFSNode node) {
        try{
            removePath(node);
        }catch(VFSPersistenceException ex){
            throw new VFSModificationException(ex);
        }
    }
    
    private void removePath(VFSNode node) {
        
        // begin transaction
        VFSPersistUtil.disableAutoCommit(conn);
        try{
            VFSFilter filter = new VFSFilterImpl();
            filter.addConstraint("length", "DESC", VFSFilterOperator.SORTBY);
            for(Iterator<VFSPathDescendant> descendants = path.getDescendants(node, filter); descendants.hasNext();){
                VFSNode descendant = descendants.next().getDescendant();
                path.removeAll(descendant);
                nodeManager.remove(descendant);
            }
            path.removeAll(node);
            nodeManager.remove(node);

            // end transaction
            VFSPersistUtil.commit(conn);
            // reenable autocommit
            VFSPersistUtil.enableAutoCommit(conn);
        }catch(VFSUncheckedException ex){
            // end transaction
            VFSPersistUtil.rollback(conn);
            // reenable autocommit
            VFSPersistUtil.enableAutoCommit(conn);
            throw ex;
        }
    }
    
}
